package com.sjx.jtt809.pojo;

import com.sjx.jtt809.util.Crc16ccittUtil;
import com.sjx.util.ConstantUtil;
import com.sjx.util.PropsUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

/**
 * 基础数据包
 */
public abstract class BasePackage {

    /**
     * 头标识
     */
    protected static final short MSG_HEAD_FLAG = 0x5b;

    // region 数据头

    /**
     * 数据长度(包括头标识、数据头、数据体和尾标识)
     */
    protected long msgLength;

    /**
     * 报文序列号，自增。
     * 占用四个字节，为发送信息的序列号，用于接收方检测是否有信息的丢失，上级平台
     * 和下级平台接自己发送数据包的个数计数，互不影响。程序开始运行时等于零，发送第一帧
     * 数据时开始计数，到最大数后自动归零
     */
    private static int internalMsgNo = 0;
    protected int msgSn;

    /**
     * 业务数据类型
     */
    protected int msgId;

    /**
     * 下级平台接入码，上级平台给下级平台分配唯一标识码
     */
    protected long msgGesscenterId;

    /**
     * 协议版本好标识，上下级平台之间采用的标准协议版
     * 编号；长度为 3 个字节来表示，0x01 0x02 0x0F 标识
     * 的版本号是 v1.2.15，以此类推
     */
    protected byte[] versionFlag = {0, 0, 1};

    /**
     * 报文加密标识位 b: 0 表示报文不加密，1 表示报文加密。
     */
    protected short encryptFlag = 1;

    /**
     * 数据加密的密匙，长度为 4 个字节。
     */
    protected long encryptKey;

    // endregion

    /**
     * 数据 CRC 校验码
     */
    protected int crcCode;

    /**
     * 尾标识
     */
    protected static final short MSG_END_FLAG = 0x5d;

    /**
     * 报文中除数据体外，固定的数据长度
     */
    public static final short MSG_FIX_LENGTH = 26;

    /**
     * 数据体长度
     */
    protected int msgBodyLength;

    public BasePackage() {

    }

    public BasePackage (int msgId) {
        //下行报文需要填充报文序列号
        synchronized((Integer)internalMsgNo) {
            if(internalMsgNo == Integer.MAX_VALUE){
                internalMsgNo = 0;
            }
        }
        this.msgSn = ++internalMsgNo;
        this.msgId = msgId;
    }

    // region 属性 Getter and Setter

    public long getMsgLength() {
        return msgLength;
    }

    public void setMsgLength(long msgLength) {
        this.msgLength = msgLength;
    }

    public static int getInternalMsgNo() {
        return internalMsgNo;
    }

    public static void setInternalMsgNo(int internalMsgNo) {
        BasePackage.internalMsgNo = internalMsgNo;
    }

    public int getMsgSn() {
        return msgSn;
    }

    public void setMsgSn(int msgSn) {
        this.msgSn = msgSn;
    }

    public int getMsgId() {
        return msgId;
    }

    public void setMsgId(int msgId) {
        this.msgId = msgId;
    }

    public long getMsgGesscenterId() {
        return msgGesscenterId;
    }

    public void setMsgGesscenterId(long msgGesscenterId) {
        this.msgGesscenterId = msgGesscenterId;
    }

    public byte[] getVersionFlag() {
        return versionFlag;
    }

    public void setVersionFlag(byte[] versionFlag) {
        this.versionFlag = versionFlag;
    }

    public short getEncryptFlag() {
        return encryptFlag;
    }

    public void setEncryptFlag(short encryptFlag) {
        this.encryptFlag = encryptFlag;
    }

    public long getEncryptKey() {
        return encryptKey;
    }

    public void setEncryptKey(long encryptKey) {
        this.encryptKey = encryptKey;
    }

    public int getCrcCode() {
        return crcCode;
    }

    public void setCrcCode(int crcCode) {
        this.crcCode = crcCode;
    }

    public int getMsgBodyLength() {
        return msgBodyLength;
    }

    public void setMsgBodyLength(int msgBodyLength) {
        this.msgBodyLength = msgBodyLength;
    }

    // endregion

    /**
     * 数据编码
     *
     * @return
     */
    public ByteBuf encode() {
        // 创建数据包
        ByteBuf buf = Unpooled.buffer(MSG_FIX_LENGTH + msgBodyLength);

        // region 写入数据头

        /** 数据长度(包括头标识、数据头、数据体和尾标识) */
        buf.writeInt(buf.capacity());

        /** 报文序列号 */
        buf.writeInt(getMsgSn());

        /** 业务数据类型 */
        buf.writeShort(getMsgId());

        /** 下级平台接入码，上级平台给下级平台分配唯一标识码 */
        buf.writeInt(PropsUtil.getConfigInstance().getInt(ConstantUtil.JTT809_FACTORY_ACCESS_CODE));

        /** 协议版本号标识 */
        buf.writeBytes(getVersionFlag());

        /** 报文加密标识位 */
        buf.writeByte(this.getEncryptFlag());

        /** 数据加密的密匙 */
        buf.writeInt((int) this.getEncryptKey());

        // endregion

        // 写入数据体
        encodeImpl(buf);

        // region 写入crc校验码

        ByteBuf finalBuffer = Unpooled.copiedBuffer(buf);
        byte[] b = Unpooled.buffer(finalBuffer.readableBytes()).array();
        finalBuffer.getBytes(0, b);
        finalBuffer.writeShort(Crc16ccittUtil.toCRC16_CCITT(b));

        // endregion

        // region 报文转义

        byte[] bytes = Unpooled.copiedBuffer(finalBuffer).array();
        ByteBuf headFormatedBuffer = Unpooled.buffer(finalBuffer.readableBytes());
        formatBuffer(bytes, headFormatedBuffer);
        ByteBuf buffera = Unpooled.buffer(headFormatedBuffer.readableBytes() + 2);

        // endregion

        // 写入头标识
        buffera.writeByte(MSG_HEAD_FLAG);

        buffera.writeBytes(headFormatedBuffer);

        // 写入尾标识
        buffera.writeByte(MSG_END_FLAG);

        return buffera;
    }

    /**
     * 具体指令数的据体编码实现
     *
     * @param buf
     */
    protected abstract void encodeImpl(ByteBuf buf);

    /**
     * 补全位数不够的定长参数 有些定长参数，实际值长度不够，在后面补0x00
     *
     * @param length
     * @param pwdByte
     * @return
     */
    protected byte[] getBytesWithLengthAfter(int length, byte[] pwdByte) {
        byte[] lengthByte = new byte[length];
        for (int i = 0; i < pwdByte.length; i++) {
            lengthByte[i] = pwdByte[i];
        }
        for (int i = 0; i < (length - pwdByte.length); i++) {
            lengthByte[pwdByte.length + i] = 0x00;
        }
        return lengthByte;
    }

    /**
     * 报文转义
     * @param bytes
     * @param formatBuffer
     */
    private static void formatBuffer(byte[] bytes, ByteBuf formatBuffer) {
        for (byte b : bytes) {
            switch (b) {
                case 0x5b:
                    byte[] formatByte0x5b = new byte[2];
                    formatByte0x5b[0] = 0x5a;
                    formatByte0x5b[1] = 0x01;
                    formatBuffer.writeBytes(formatByte0x5b);
                    break;
                case 0x5a:
                    byte[] formatByte0x5a = new byte[2];
                    formatByte0x5a[0] = 0x5a;
                    formatByte0x5a[1] = 0x02;
                    formatBuffer.writeBytes(formatByte0x5a);
                    break;
                case 0x5d:
                    byte[] formatByte0x5d = new byte[2];
                    formatByte0x5d[0] = 0x5e;
                    formatByte0x5d[1] = 0x01;
                    formatBuffer.writeBytes(formatByte0x5d);
                    break;
                case 0x5e:
                    byte[] formatByte0x5e = new byte[2];
                    formatByte0x5e[0] = 0x5e;
                    formatByte0x5e[1] = 0x02;
                    formatBuffer.writeBytes(formatByte0x5e);
                    break;
                default:
                    formatBuffer.writeByte(b);
                    break;
            }
        }
    }

}
